package edu.hawaii.halealohacli.command;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import javax.xml.datatype.XMLGregorianCalendar;
import org.wattdepot.client.BadXmlException;
import org.wattdepot.client.MiscClientException;
import org.wattdepot.client.NotAuthorizedException;
import org.wattdepot.client.ResourceNotFoundException;
import org.wattdepot.client.WattDepotClient;
import org.wattdepot.resource.source.jaxb.Source;

/**
 * Determines if a given source has met a goal of power conservation.
 * 
 * @author Branden Ogata
 * 
 */
public class MonitorGoal extends TimerTask implements Command {
  /**
   * The percentage that the source must decrease to meet its goal. Defaults to 5%.
   * 
   */
  private int goal = 5;

  /**
   * The interval at which to measure power. Defaults to 10 seconds.
   * 
   */
  private int interval = 10;

  /**
   * The set of Baseline instances obtained from SetBaseline.
   * 
   */
  private List<Baseline> baselines = new ArrayList<Baseline>();

  /**
   * The latest result from printResults.
   * 
   */
  private String latestResult;

  /**
   * The client used for the current command.
   * 
   */
  private WattDepotClient taskClient;

  /**
   * The source used for the current command.
   * 
   */
  private Source taskSource;

  /**
   * Outputs the time of the operation, the current power consumption, and if the goal was met. The
   * goal is defined as the percentage that power consumption must decrease.
   * 
   * @param client The WattDepotClient to connect to.
   * @param source The source to obtain baseline values for.
   * @param days The date on which to obtain baseline values.
   * 
   * @throws Exception Handles any exceptions that do occur.
   * 
   */
  @Override
  public void printResults(WattDepotClient client, Source source, XMLGregorianCalendar... days)
      throws Exception {

    // Ensures that the connection is valid (at least in theory)
    if (client.isHealthy()) {
      System.out.format("Connected to: %s%n", client.getWattDepotUri());
      // If there are no parameters provided for days, then run normally
      if (days.length == 0) {
        this.taskClient = client;
        this.taskSource = source;
        try {
          System.out.println("Monitoring power for " + this.taskSource.getName() + " at every "
              + this.interval + " seconds. Press enter to stop.");
          Timer timer = new Timer();
          timer.scheduleAtFixedRate(this, interval * 1000, interval * 1000);
          while (System.in.available() == 0) {
            run();
            Thread.sleep(interval * 1000);
          }
          timer.cancel();
          Thread.sleep(200);
          System.out.println("Terminated monitoring");
        }
        catch (IllegalArgumentException e) {
          System.out.println("Error in arguments");
        }
        catch (IllegalStateException e) {
          System.out.println("Error in timer");
        }
        catch (InterruptedException e) {
          System.out.println("Error in threads");
        }
      }
      // Else debugging case: run only once
      else {
        String sourceName = source.getName();

        // Records if the operation was successful
        boolean baselineExists = false;

        // If sourceName exists in the set of baselines,
        // then a baseline exists for the given source
        for (Baseline validate : this.baselines) {
          if (validate.getSource().equals(sourceName)) {
            // Indicate that the baseline does exist
            baselineExists = true;

            // Calculate the current date and power consumption for the source
            XMLGregorianCalendar today = client.getLatestSensorData(sourceName).getTimestamp();
            double powerConsumption = client.getLatestPowerConsumed(sourceName) / 1000;

            // Adjust the hour for AM/PM and define AM/PM
            int hour = today.getHour() % 12;
            String half = "AM";

            if (hour == 0) {
              hour = 12;
            }

            if (today.getHour() >= 12) {
              half = "PM";
            }

            this.latestResult =
                String
                    .format(
                        "On %02d:%02d:%02d%s of %02d-%02d-%02d, Source %s is currently consuming "
                            + "%.1f kW, which ", hour, today.getMinute(), today.getSecond(), half,
                        today.getYear(), today.getMonth(), today.getDay(), sourceName,
                        powerConsumption);

            // Determine if the source has met power conservation goals
            double powerGoal =
                validate.getValues()[today.getHour()]
                    - (this.goal * 0.01 * validate.getValues()[today.getHour()]);

            if (powerConsumption < powerGoal) {
              this.latestResult +=
                  String.format("meets the power conservation goal of %.1f kW for the source.",
                      powerGoal);
            }
            else {
              this.latestResult +=
                  String.format(
                      "does not meet the power conservation goal of %.1f kW for the source.",
                      powerGoal);
            }
          }
        }

        // If baselineExists is still false, then cancel and print error message
        if (!baselineExists) {
          this.latestResult =
              "The given baseline could not be found.  Please ensure that "
                  + "set-baseline was called before calling monitor-goal.";
          /*
           * System.out.println("The given baseline could not be found.  Please ensure that " +
           * "set-baseline was called before calling monitor-goal.");
           */
        }

        // Print out whatever message was produced
        System.out.println(this.latestResult);

      }
    }
    else {
      System.out.format("Could not connect to: %s%n", source.getName());
    }
  }

  /**
   * Returns the current goal.
   * 
   * @return this.goal The current goal value.
   * 
   */
  public int getGoal() {
    return this.goal;
  }

  /**
   * Sets the current goal.
   * 
   * @param goal The value to set the goal.
   * 
   */
  public void setGoal(int goal) {
    this.goal = goal;
  }

  /**
   * Returns the current interval.
   * 
   * @return this.interval The current interval value.
   * 
   */
  public int getInterval() {
    return this.interval;
  }

  /**
   * Sets the current interval.
   * 
   * @param interval The value to set the interval.
   * 
   */
  public void setInterval(int interval) {
    this.interval = interval;
  }

  /**
   * Adds a Baseline instance to baselines. If a Baseline with the same source already exists,
   * overwrite.
   * 
   * @param baseline The Baseline instance to add to the set of baselines.
   * 
   */
  public void addBaseline(Baseline baseline) {
    // If a Baseline with the same source already exists, then remove it from the list
    boolean exists = false;
    int currentSource = 0;

    while ((!exists) && (currentSource < this.baselines.size())) {
      if (this.baselines.get(currentSource).getSource().equals(baseline.getSource())) {
        exists = true;
        this.baselines.remove(currentSource);
      }
      // Need to increment if it is not the same source
      // If it is the same source, then removing it would mean that the "next"
      // source is now currentSource, so incrementing would not be necessary
      else {
        currentSource++;
      }
    }

    this.baselines.add(baseline);
  }

  /**
   * Returns the baseline value at the given time for a given source.
   * 
   * @param name The name of the source to examine.
   * 
   * @return value If a baseline value exists for the given source name, return that value. Else
   * return -1.
   * 
   */
  public double getBaselineValue(String name) {
    double value = -1;

    // Calculate the current date and power consumption for the source
    Calendar date = Calendar.getInstance();
    int hour = date.get(Calendar.HOUR_OF_DAY);

    // Search through all Baseline instances
    for (int i = 0; i < this.baselines.size(); i++) {
      if (this.baselines.get(i).getSource().equals(name)) {
        value = this.baselines.get(i).getValues()[hour];
      }
    }

    return value;
  }

  /**
   * Returns the latest result from a call to printResults. This is used for testing.
   * 
   * @return this.latestResult The String containing the most recent results from printResults.
   * 
   */
  public String getLatestResult() {
    return this.latestResult;
  }

  /**
   * The run method for TimerTask.
   * 
   */
  public void run() {
    try {
      String sourceName = this.taskSource.getName();

      // Records if the operation was successful
      boolean baselineExists = false;

      // If sourceName exists in the set of baselines,
      // then a baseline exists for the given source
      for (Baseline validate : this.baselines) {
        if (validate.getSource().equals(sourceName)) {
          // Indicate that the baseline does exist
          baselineExists = true;

          // Calculate the current date and power consumption for the source
          XMLGregorianCalendar today =
              this.taskClient.getLatestSensorData(sourceName).getTimestamp();
          double powerConsumption = this.taskClient.getLatestPowerConsumed(sourceName) / 1000;

          // Adjust the hour for AM/PM and define AM/PM
          int hour = today.getHour() % 12;
          String half = "AM";

          if (hour == 0) {
            hour = 12;
          }

          if (today.getHour() >= 12) {
            half = "PM";
          }

          this.latestResult =
              String.format(
                  "On %02d:%02d:%02d%s of %02d-%02d-%02d, Source %s is currently consuming "
                      + "%.1f kW, which ", hour, today.getMinute(), today.getSecond(), half,
                  today.getYear(), today.getMonth(), today.getDay(), sourceName, powerConsumption);

          // Determine if the source has met power conservation goals
          double powerGoal =
              validate.getValues()[today.getHour()]
                  - (this.goal * 0.01 * validate.getValues()[today.getHour()]);

          if (powerConsumption < powerGoal) {
            this.latestResult +=
                String.format("meets the power conservation goal of %.1f kW for the source.",
                    powerGoal);
          }
          else {
            this.latestResult +=
                String.format(
                    "does not meet the power conservation goal of %.1f kW for the source.",
                    powerGoal);
          }
        }
      }

      // If baselineExists is still false, then cancel and print error message
      if (!baselineExists) {
        this.latestResult =
            "The given baseline could not be found.  Please ensure that "
                + "set-baseline was called before calling monitor-goal.";
      }

      // Print out whatever message was produced
      System.out.println(this.latestResult);
    }
    catch (NotAuthorizedException e1) {
      e1.printStackTrace();
    }
    catch (ResourceNotFoundException e1) {
      e1.printStackTrace();
    }
    catch (BadXmlException e1) {
      e1.printStackTrace();
    }
    catch (MiscClientException e1) {
      e1.printStackTrace();
    }
  }
}